package primitives;


import java.util.ArrayList;

import javax.vecmath.Matrix4d;
import javax.vecmath.Vector2d;
import javax.vecmath.Vector3d;
import javax.vecmath.Vector4d;

import shaders.MatteShader;
import shaders.Shader;

import essential.Axis;
import essential.KDTree;
import essential.KDTreeNode;
import essential.Ray;
import essential.RayCastResult;


public class Mesh implements Renderable {
	protected Vector3d origin = new Vector3d(0, 0, 0);
	protected KDTreeNode spacePartition;
	public Shader shader;
	protected AxisAlignedBoundingBox aabb;
	protected ArrayList<Renderable> trigs;
	Matrix4d transform = new Matrix4d();
	private Matrix4d tmpMat = new Matrix4d();
	private Vector4d tmpVec = new Vector4d(); 
	private Vector3d tmpVecA = new Vector3d();
	private Vector3d tmpVecB = new Vector3d();
	private Vector3d tmpVecC = new Vector3d(); 	

	public Renderable copy() {
		Mesh m = new Mesh();

		m.origin = new Vector3d(origin);
		m.spacePartition = spacePartition.copy();
		m.aabb = new AxisAlignedBoundingBox();
		m.aabb.setA(aabb.getA());
		m.aabb.setB(aabb.getB());
		
		m.trigs = new ArrayList<Renderable>();
		for (int i=0; i<trigs.size(); i++)
			m.trigs.add(trigs.get(i).copy());
		
		m.setShader(getShader().copy());
		m.transform = new Matrix4d(transform);
		return m;
	}

	public Mesh() {}
	
	public Mesh(Vector3d[] points, int[] triangles, Vector2d[] uvs, Vector3d[] normals) {
		set(points, triangles, uvs, normals);
		aabb = new AxisAlignedBoundingBox(points);
		spacePartition = KDTree.BuildKDTree(trigs, aabb, triangles.length/3);
		shader = new MatteShader();
	}

	public void set(Vector3d[] points, int[] triangles, Vector2d[] uvs, Vector3d[] normals) {
		//material = new Material();
		aabb = new AxisAlignedBoundingBox(points);
		trigs = new ArrayList<Renderable>();
		transform.setIdentity();

		//TODO Considerar normales
		Vertex a = new Vertex();
		Vertex b = new Vertex();
		Vertex c = new Vertex();

		for (int i=0; i<triangles.length; i+=3) {
			a.xyz.set(points[triangles[i+0]]);
			b.xyz.set(points[triangles[i+1]]);
			c.xyz.set(points[triangles[i+2]]);
			
			a.uv.set(uvs[triangles[i+0]]);
			b.uv.set(uvs[triangles[i+1]]);
			c.uv.set(uvs[triangles[i+2]]);
			
			a.normal.set(normals[triangles[i+0]]);
			b.normal.set(normals[triangles[i+1]]);
			c.normal.set(normals[triangles[i+2]]);
			Triangle t = new Triangle(a, b, c);
			t.setShader(shader);
			trigs.add(t);
		}
	}
	
	void recalcSpacePartition() {
		Vector3d[] points = new Vector3d[trigs.size() * 3];
		
		for (int i=0; i<trigs.size(); i++) {
			points[i*3 + 0] = ((Triangle)trigs.get(i)).a.xyz;
			points[i*3 + 1] = ((Triangle)trigs.get(i)).b.xyz;
			points[i*3 + 2] = ((Triangle)trigs.get(i)).c.xyz;
			//trigs.get(i).recalcAABB();
		}
		
		aabb = new AxisAlignedBoundingBox(points);
		spacePartition = KDTree.BuildKDTree(trigs, aabb, trigs.size());
	}

	@Override
	public RayCastResult intersect(Ray ray) {
		Triangle t = (Triangle)spacePartition.intersect(ray);
		
		if (t == null)
			return RayCastResult.MISS;
		
		//	ray.impactNormal = t.getNormal(ray.getIntersectionPoint());
		ray.setLastRenderable(this);
	//	ray.impactColor = getShader().getColor(ray);
		return RayCastResult.HIT;
	}

	@Override
	public AxisAlignedBoundingBox getAABB() {
		return aabb;
	}

	public Shader getShader() {
		return shader;
	}
	

	@Override
	public Vector3d getOrigin() {
		return origin;
	}

	@Override
	public void rotate(Axis axis, double angles) {
		tmpMat.setIdentity();

		switch(axis) {
		case X:
			tmpMat.rotX(angles);
			break;
		case Y:
			tmpMat.rotY(angles);
			break;
		case Z:
			tmpMat.rotZ(angles);
			break;
		}

 		transform.mul(tmpMat, transform);
	}
	
	public Mesh clone(){
		return null;
	}

	@Override
	public void scale(Vector3d v) {
		tmpMat.setIdentity();
		tmpMat.m00 = v.x;
		tmpMat.m11 = v.y;
		tmpMat.m22 = v.z;
		transform.mul(tmpMat, transform);
	}

	@Override
	public void translate(Vector3d pos) {
		tmpMat.setIdentity();
		tmpMat.setTranslation(pos);
		transform.mul(tmpMat, transform);
	}

	@Override
	public void freezeTransform() {
		tmpVec.set(origin);
		tmpVec.w = 1.0;
		transform.transform(tmpVecA);
		origin.set(tmpVecA.x, tmpVecA.y, tmpVecA.z);

		//Transform vertices
		transform.transform(origin);
		for (int i=0; i<trigs.size(); i++) {
			tmpVec.set(((Triangle)trigs.get(i)).a.xyz);
			tmpVec.w = 1.0;
			transform.transform(tmpVec);
			tmpVecA.set(tmpVec.x, tmpVec.y, tmpVec.z);

			tmpVec.set(((Triangle)trigs.get(i)).b.xyz);
			tmpVec.w = 1.0;
			transform.transform(tmpVec);
			tmpVecB.set(tmpVec.x, tmpVec.y, tmpVec.z);

			tmpVec.set(((Triangle)trigs.get(i)).c.xyz);
			tmpVec.w = 1.0;
			transform.transform(tmpVec);
			tmpVecC.set(tmpVec.x, tmpVec.y, tmpVec.z);

			((Triangle)trigs.get(i)).set(tmpVecA, tmpVecB, tmpVecC);
			((Triangle)trigs.get(i)).recalcAABB();
		}
		
		//Recalculate AABBs and KDTree
		recalcSpacePartition();
	}

   public void recalcAABB() {
		recalcSpacePartition();
	}

	@Override
	public void apply(Transform t) {
		transform.mul(t.matrix, transform);
	}

	@Override
	public void set(Transform t) {
		transform.setIdentity();
		transform.mul(t.matrix, transform);
	}

	@Override
	public void setShader(Shader shader) {
		for (int i=0; i<trigs.size(); i++) {
			trigs.get(i).setShader(shader);
		}

		this.shader = shader;
	}
}
